using System;
using System.Collections.Generic;
using System.Text;
using System.IO.Ports;
using System.IO;
using System.Timers;

namespace DTLAutomateLib
{
	public delegate void ResponseCallback(string response);
	public delegate void RS232Error(MarklinCommand c, string d, string message);
	
	[global::System.Serializable]
	public class MarklinInterfaceNotRespondingException : IOException
	{
		//
		// For guidelines regarding the creation of new exception types, see
		//    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp
		// and
		//    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp
		//

		public MarklinInterfaceNotRespondingException() { }
		public MarklinInterfaceNotRespondingException(string message) : base(message) { }
		public MarklinInterfaceNotRespondingException(string message, Exception inner) : base(message, inner) { }
		protected MarklinInterfaceNotRespondingException(
		  System.Runtime.Serialization.SerializationInfo info,
		  System.Runtime.Serialization.StreamingContext context)
			: base(info, context) { }
	}

	public struct MarklinCommand
	{
		public string command;
		public bool responseNeeded;
		public ResponseCallback callback;

		public MarklinCommand(string command)
		{
			this.command = command;
			this.responseNeeded = false;
			this.callback = null;
		}
		public MarklinCommand(string command, ResponseCallback callback)
		{
			this.command = command;
			this.responseNeeded = true;
			this.callback = callback;
		}
	}


	public class DriverRS232 : IDisposable
	{
		private string portName;
		private SerialPort port;
		private Queue<MarklinCommand> commandQueue;
		private Queue<MarklinCommand> responseQueue;
		private Timer timer;
		private bool connected = false;

		#region public events

		public event RS232Error recieveError;

		#endregion

		#region public properties

		public bool Connected
		{
			get { return this.connected; }
		}

		#endregion

		#region public methods

		/// <summary>
		/// Creates a new driver for use with RS232 interface to the Marklin 6023 central control-i
		/// </summary>
		/// <param name="portName">The name of the com port (i.e. "com1")</param>
		public DriverRS232(string portName)
		{
			this.portName = portName;
			this.commandQueue = new Queue<MarklinCommand>();
			this.responseQueue = new Queue<MarklinCommand>();
			try
			{
				this.port = new SerialPort(portName, 2400, Parity.None, 8, StopBits.Two);
			}
			catch (Exception e)
			{
				DebugMethods.printDebug(e.Message);
			}
			if (port == null) throw new IOException(String.Format("Error opening {0} port", portName));

			this.port.NewLine = "\r";
			this.port.Handshake = Handshake.RequestToSend;
			this.port.ReadTimeout = 200;
			this.port.WriteTimeout = 200;
		}

		/// <summary>
		/// Starts the connection and tests for the presence of the interface
		/// </summary>
		public void start()
		{
			this.port.Open();
			try
			{
				this.testConnection(port);
			}
			catch (MarklinInterfaceNotRespondingException e)
			{
				this.port.Close();
				throw e;
			}

			this.port.DataReceived += new SerialDataReceivedEventHandler(this.port_DataReceived);

			timer = new Timer(50);
			timer.Elapsed += new ElapsedEventHandler(timer_Tick);
			timer.Enabled = true;
		}

		void timer_Elapsed(object sender, ElapsedEventArgs e)
		{
			
		}

		/// <summary>
		/// Sends a command.  This command should NOT produce a response.
		/// </summary>
		/// <param name="command">The command to be sent</param>
		public void sendCommand(string command)
		{
			this.commandQueue.Enqueue(new MarklinCommand(command));
		}

		/// <summary>
		/// Sends a command that produces a response.  This response is sent through the callback function given.
		/// </summary>
		/// <param name="command">The command to be sent</param>
		/// <param name="callback">The function to call when a response is received</param>
		public void sendCommand(string command, ResponseCallback callback)
		{
			this.commandQueue.Enqueue(new MarklinCommand(command, callback));
		}

		#endregion

		#region private methods

		/// <summary>
		/// Function to receive SerialDataReceived events.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void port_DataReceived(object sender, SerialDataReceivedEventArgs e)
		{
			string response = null;
			MarklinCommand thisResponse = new MarklinCommand();

			try
			{
				response = this.port.ReadLine();
				if (response != null)
				{
					thisResponse = responseQueue.Dequeue();
					thisResponse.callback(response);
				}
			}
			catch (InvalidOperationException ioe)
			{
				DebugMethods.printDebug(ioe.Message);
			}
			catch (IOException ioe)
			{
				DebugMethods.printDebug(ioe.Message);
			}
			catch (TimeoutException ioe)
			{
				DebugMethods.printDebug(ioe.Message);
				if (this.recieveError != null)
				{
					this.recieveError(thisResponse, response, ioe.Message);
				}
			}
		}

		/// <summary>
		/// Sends a command out.
		/// </summary>
		/// <param name="sender"></param>
		/// <param name="e"></param>
		void timer_Tick(object sender, EventArgs e)
		{
			if (commandQueue.Count <= 0) return;

			MarklinCommand thisCommand = commandQueue.Dequeue();
			try
			{
				this.port.WriteLine(thisCommand.command);
			}
			catch (Exception ev)
			{
				Console.WriteLine(ev.Message);
			}

			if (thisCommand.responseNeeded)
			{
				this.responseQueue.Enqueue(thisCommand);
				//Console.WriteLine(this.port.ReadLine());
			}
		}

		/// <summary>
		/// Tests the presence of a marklin 6023 central control-i on the given port.
		/// </summary>
		/// <param name="port">the port to test on</param>
		private void testConnection(SerialPort port)
		{
			try
			{
				port.WriteLine("a1");
			}
			catch (TimeoutException e)
			{
				throw new MarklinInterfaceNotRespondingException("Cannot write to " + port.PortName + "\n" + e.Message);
			}
			try
			{
				port.ReadLine();
			}
			catch (TimeoutException e)
			{
				throw new MarklinInterfaceNotRespondingException("No response to \"a1\" command\n" + e.Message);
			}
		}

		#endregion

		#region IDisposable Members

		public void Dispose()
		{
			if (this.port.IsOpen) this.port.Close();
		}

		#endregion
	}
}
